package com.xiaomaoguai.fcp.pre.kepler.redis.lock.service.impl;

import com.xiaomaoguai.fcp.pre.kepler.redis.lock.function.LockFunction;
import com.xiaomaoguai.fcp.pre.kepler.redis.lock.function.LockFunctionGeneric;
import com.xiaomaoguai.fcp.pre.kepler.redis.lock.function.LockFunctionGenericWithArgs;
import com.xiaomaoguai.fcp.pre.kepler.redis.lock.function.LockFunctionWithArgs;
import com.xiaomaoguai.fcp.pre.kepler.redis.lock.service.LockService;
import lombok.AllArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;

import java.io.Serializable;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * Redis 缓存锁实现
 *
 * @author Jerry.Chen
 * @since 2018年5月21日 下午1:13:16
 */
@AllArgsConstructor
public class RedisLockServiceImpl implements LockService {

	/**
	 * Redis data access Template
	 */
	private RedisTemplate<Serializable,String> redisTemplate;

	/**
	 * 缓存锁的时间单位
	 */
	private TimeUnit timeUnit;

	@Override
	public boolean getLock(Serializable key, int expireTime, TimeUnit timeUnit) {
		Boolean result = redisTemplate.opsForValue().setIfAbsent(key, "1", expireTime, timeUnit);
		return Objects.equals(result, true);
	}

	@Override
	public boolean getLock(Serializable key, int expireTime) {
		return getLock(key, expireTime, timeUnit);
	}

	@Override
	public boolean releaseLock(Serializable key) {
		Boolean delete = redisTemplate.delete(key);
		return Objects.equals(delete, true);
	}

	@Override
	public boolean executeByLock(Serializable key, int expireTime, LockFunction callback) {
		long begin = System.currentTimeMillis();
		boolean isGetLock = getLock(key, expireTime);
		try {
			if (isGetLock) {
				callback.onLockSuccess();
			} else {
				int tryLockTimes = callback.tryLockTimes();
				if (tryLockTimes <= 1) {
					callback.onLockFailed();
				} else {
					// 进行多次重试获取锁
					for (int i = 1; i < tryLockTimes; i++) {
						LockService.sleep(callback.tryLockSleepTime());
						begin = System.currentTimeMillis();
						isGetLock = getLock(key, expireTime);
						if (isGetLock) {
							break;
						}
					}
					if (isGetLock) {
						callback.onLockSuccess();
					} else {
						callback.onLockFailed();
					}
				}
			}
		} finally {
			// 如果已经获取到锁，才释放锁
			if (isGetLock) {
				long cost = System.currentTimeMillis() - begin;
				long expireTimeToMills = timeUnit.toMillis(expireTime);
				if (cost < expireTimeToMills) {
					releaseLock(key);
				}
			}
		}
		return isGetLock;
	}

	@Override
	public boolean executeByLock(Serializable key, int expireTime, LockFunctionWithArgs callback, Object... args) {
		long begin = System.currentTimeMillis();
		boolean isGetLock = getLock(key, expireTime);
		try {
			if (isGetLock) {
				callback.onLockSuccess(args);
			} else {
				int tryLockTimes = callback.tryLockTimes();
				if (tryLockTimes <= 1) {
					callback.onLockFailed(args);
				} else {
					// 进行多次重试获取锁
					for (int i = 1; i < tryLockTimes; i++) {
						LockService.sleep(callback.tryLockSleepTime());
						begin = System.currentTimeMillis();
						isGetLock = getLock(key, expireTime);
						if (isGetLock) {
							break;
						}
					}
					if (isGetLock) {
						callback.onLockSuccess(args);
					} else {
						callback.onLockFailed(args);
					}
				}
			}
		} finally {
			// 如果已经获取到锁，才释放锁
			if (isGetLock) {
				long cost = System.currentTimeMillis() - begin;
				long expireTimeToMills = timeUnit.toMillis(expireTime);
				if (cost < expireTimeToMills) {
					releaseLock(key);
				}
			}
		}
		return isGetLock;
	}

	@Override
	public <T> T executeByLock(Serializable key, int expireTime, LockFunctionGeneric<T> callback) {
		long begin = System.currentTimeMillis();
		boolean isGetLock = getLock(key, expireTime);
		try {
			if (isGetLock) {
				return callback.onLockSuccess();
			} else {
				int tryLockTimes = callback.tryLockTimes();
				if (tryLockTimes <= 1) {
					return callback.onLockFailed();
				} else {
					// 进行多次重试获取锁
					for (int i = 1; i < tryLockTimes; i++) {
						LockService.sleep(callback.tryLockSleepTime());
						begin = System.currentTimeMillis();
						isGetLock = getLock(key, expireTime);
						if (isGetLock) {
							break;
						}
					}
					if (isGetLock) {
						return callback.onLockSuccess();
					} else {
						return callback.onLockFailed();
					}
				}
			}
		} finally {
			// 如果已经获取到锁，才释放锁
			if (isGetLock) {
				long cost = System.currentTimeMillis() - begin;
				long expireTimeToMills = timeUnit.toMillis(expireTime);
				if (cost < expireTimeToMills) {
					releaseLock(key);
				}
			}
		}
	}

	@Override
	public <T> T executeByLock(Serializable key, int expireTime, LockFunctionGenericWithArgs<T> callback,
							   Object... args) {
		long begin = System.currentTimeMillis();
		boolean isGetLock = getLock(key, expireTime);
		try {
			if (isGetLock) {
				return callback.onLockSuccess(args);
			} else {
				int tryLockTimes = callback.tryLockTimes();
				if (tryLockTimes <= 1) {
					return callback.onLockFailed(args);
				} else {
					// 进行多次重试获取锁
					for (int i = 1; i < tryLockTimes; i++) {
						LockService.sleep(callback.tryLockSleepTime());
						begin = System.currentTimeMillis();
						isGetLock = getLock(key, expireTime);
						if (isGetLock) {
							break;
						}
					}
					if (isGetLock) {
						return callback.onLockSuccess(args);
					} else {
						return callback.onLockFailed(args);
					}
				}
			}
		} finally {
			// 如果已经获取到锁，才释放锁
			if (isGetLock) {
				long cost = System.currentTimeMillis() - begin;
				long expireTimeToMills = timeUnit.toMillis(expireTime);
				if (cost < expireTimeToMills) {
					releaseLock(key);
				}
			}
		}
	}

}
